from MovieLens import MovieLens
from surprise import SVD
from surprise import KNNBaseline
from surprise.model_selection import train_test_split, LeaveOneOut
from RecommenderMetrics import RecommenderMetrics

# %% 1. 初始化 MovieLens 数据集
ml = MovieLens()

print("Loading movie ratings...")
print("加载电影评分数据...")
data = ml.loadMovieLensLatestSmall()  # 加载 MovieLens 最新小型数据集，返回一个 surprise.Dataset 对象

print("\nComputing movie popularity ranks so we can measure novelty later...")
print("\n计算电影流行度排名，以便后续测量新颖性...")
rankings = ml.getPopularityRanks()

print("\nComputing item similarities so we can measure diversity later...")
print("\n计算项目相似度，以便后续测量多样性...")
fullTrainSet = data.build_full_trainset()
# 相似度选项，使用皮尔逊基线相似度，基于项目(item)
sim_options = {'name': 'pearson_baseline', 'user_based': False}
# KNN 用于计算相似度 -> 评估多样性
simsAlgo = KNNBaseline(sim_options=sim_options)
simsAlgo.fit(fullTrainSet)  # 在完整训练集上拟合模型

# %% 2. 构建推荐模型
print("\nBuilding recommendation model...")
print("\n构建推荐模型...")
# 划分训练集和测试集，测试集占 25%
trainSet, testSet = train_test_split(data, test_size=.25, random_state=1)

algo = SVD(random_state=10)  # 初始化 SVD 算法，用于降维、特征提取和提高计算性能
algo.fit(trainSet)  # 在训练集上拟合模型

print("\nComputing recommendations...")
print("\n生成推荐...")
predictions = algo.test(testSet)

print("\nEvaluating accuracy of model...")
print("\n评估模型准确性...")
print("RMSE:", RecommenderMetrics.RMSE(predictions))
print("MAE:", RecommenderMetrics.MAE(predictions))

# %% 3. 评估 Top-N 推荐
print("\nEvaluating top-10 recommendations...")
print("\n评估 Top-10 推荐...")

# 每个用户留出一个评分为测试
# Set aside one rating per user for testing
LOOCV = LeaveOneOut(n_splits=1, random_state=1)  # 交叉验证迭代器

for trainSet, testSet in LOOCV.split(data):
    print("使用留一法生成推荐...")
    print("Computing recommendations with leave-one-out...")

    # 在不含留出评分的训练集上训练模型
    # Train model without Left-out ratings
    algo.fit(trainSet)

    # 仅对留出评分进行预测
    # Predicts ratings for Left-out ratings only
    print("Predict ratings for left-out set...")
    leftOutPredictions = algo.test(testSet)

    # 对训练集中不存在的所有评分进行预测
    # Build predictions for all ratings not in the training set
    print("Predict all missing ratings...")
    # Return a list of ratings that can be used as a testset in the
    #         :meth:`test() <surprise.prediction_algorithms.algo_base.AlgoBase.test>`
    #         method.
    bigTestSet = trainSet.build_anti_testset()
    allPredictions = algo.test(bigTestSet)

    # 为每个用户计算 Top-10 推荐
    # Compute top 10 recs for each user
    print("Compute top 10 recs per user...")
    topNPredicted = RecommenderMetrics.GetTopN(allPredictions, n=10)

    # 查看我们推荐的电影中用户实际评分过的频率
    # See how often we recommended a movie the user actually rated
    print("\nHit Rate:", RecommenderMetrics.HitRate(topNPredicted, leftOutPredictions))

    # 按评分值拆分命中率
    # Break down hit rate by rating value
    print("\nrHR (Hit Rate by Rating value):",
          RecommenderMetrics.RatingHitRate(topNPredicted, leftOutPredictions))

    # 查看我们推荐的电影中用户实际喜欢（评分为 4 或更高）的频率
    # See how often we recommended a movie the user actually Liked
    print("\ncHR (Cumulative Hit Rate,rating >= 4):",
          RecommenderMetrics.CumulativeHitRate(topNPredicted, leftOutPredictions, 4.0))

    # 计算 ARHR（平均倒数命中排名）
    # Compute ARHR
    print("\nARHR (Average Reciprocal Hit Rank):",
          RecommenderMetrics.AverageReciprocalHitRank(topNPredicted, leftOutPredictions))

# %% 4. 完整推荐（无保留）
print("\n计算完整推荐（无保留）...")
algo.fit(fullTrainSet)  # 在完整训练集上拟合模型
bigTestSet = fullTrainSet.build_anti_testset()  # 构建反测试集（包含所有未评分项）
allPredictions = algo.test(bigTestSet)  # 对反测试集进行预测

topNPredicted = RecommenderMetrics.GetTopN(allPredictions, n=10)  # 获取每个用户的 Top-10 推荐

# 打印用户覆盖率（预测评分为 4.0 或更高的最小值）
# Print user coverage with a minimum predicted rating of 4.0:
print("\n用户覆盖率:",
      RecommenderMetrics.UserCoverage(topNPredicted, fullTrainSet.n_users, ratingThreshold=4.0))
print("\nUser coverage:",
      RecommenderMetrics.UserCoverage(topNPredicted, fullTrainSet.n_users, ratingThreshold=4.0))

# 测量推荐的多样性
# Measure diversity of recommendations:
print("\n多样性:", RecommenderMetrics.Diversity(topNPredicted, simsAlgo))
print("\nDiversity:", RecommenderMetrics.Diversity(topNPredicted, simsAlgo))

# 测量新颖性（推荐项目的平均流行度排名）
# Measure novelty (average popularity rank of recommendations):
print("\n新颖性（平均流行度排名）:", RecommenderMetrics.Novelty(topNPredicted, rankings))
print("\nNovelty (average popularity rank):", RecommenderMetrics.Novelty(topNPredicted, rankings))
